package com.sholmes.android.scribbler;

import java.util.Observable;
import java.util.Observer;

import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;

import com.sholmes.android.scribbler.view.ManualDriveView;
/**
 * This activity allows the user to manually control the scribbler robot.  
 * 
 * The following controls are available:
 * 	1) Manual drive based off of accelerometer output
 * 	2) Stopping the robot
 * 	3) Changing the forward direction of the robot
 * 	4) Memory
 * 
 *  Option 1) This is the default behavior of the ManualDrive Activity
 *  	when the user enters the manual drive screen a count down from 5 will
 *  	begin after which the accelerometer values will be used to control 
 *  	all movements of the robot.  When the robot is is the ScribblerActivity forward
 *  	facing direction the IR sensors will be used to determine if objects 
 *  	are ahead,left or right.
 *  
 *  Option 2) Stopping the robot is something that needs to be performed at a 
 *  	moments notice. the user has the ability to do this by pressing anywhere 
 *  	on the screen and holding down.  The robot will stay in the stopped 
 *  	position until the user releases their finger from the screen.  A message
 *  	will also be displayed showing that the robot is stopped.
 *  
 *  Option 3) Changing the forward direction is controlled by the menu interface 
 *  	built into the Nexus One.  By selecting this interface a dialog will 
 *  	be presented allowing the user to select "Fluke Forward" or "ScribblerActivity 
 *  	Forward"  the default will have the scribbler facing forward for usage
 *  	if its front facing IR sensors.
 *  
 *   //TODO: Option 4 if time allows
 *   Option 4) Memory.  Selecting the alt menu as you do in option 3 a user can also
 *   	select a record option that will record the robots movements.  Within the
 *   	record session pressing on the touch screen interface as you do with stop 
 *   	stops the recording and saves it by date in the recorded behaviors.
 *   	DREAMING...
 *   
 *   Look and Feel)
 *   	As objects come into range of the sensors the section will cover with a 
 *   	transparent green red or yellow based on the sensor value.  As you approach
 *   	the object the transparent screen will grow closer to the bottom of the phone.
 *   	
 *   	The display will also update the Rotation and Speed as changed based off of 
 *   	the accelerometer value (post processed).
 *   	
 *   	In addition to the rotation values indicated there will be an arrow pointing
 *   	into the direction that the robot is currently heading.  As the robots speed
 *   	increases the arrow will grow longer.
 *   
 *   	If there is a point in time where the robot is reporting STALLED by its sensors
 *   	the screen will glass over reporting STALLED until the condition is remedied. 
 * 
 * 
 * @author Justin A. McCoy {@link mailto:justin.mccoy@acm.org}
 *
 */
public class ManualDriveActivity extends BasicRobotActivity implements Observer, SensorEventListener{
	private static final String TAG ="ManualDriveActivity: ";
	
	private ManualDriveView mManualDriveView;
	private SensorProcessor sensorProcessor;
	private int previousSpeed = 0;
	
	
	//Interface to the phones sensors.
	private SensorManager sensorManager;
	
	private float[] accelerometerValues = new float[3];
	private float[] magneticValues = new float[3];
	
	
	 @Override
	    public void onCreate(Bundle savedInstanceState) {
	        super.onCreate(savedInstanceState);
	        setContentView(R.layout.manual_behavior);	        
	        init();		// Setup listeners, views and phone components.	        	 
	    }
	 
	private void init(){		 
		 sensorProcessor = new SensorProcessor();
		 sensorProcessor.addObserver(this);
		 
	     mManualDriveView = (ManualDriveView)findViewById(R.id.manualDriveView);

		 
		 //Initialize the sensor manager
		 sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
		 
	 }
 
	@SuppressWarnings("static-access")
	private float[] calculateOrientation(){
		 float[] values = new float[3];
		 float[] R = new float[9];
		 float[] outR = new float[9];
		 
		 sensorManager.getRotationMatrix(R, null, accelerometerValues, magneticValues);
		 sensorManager.remapCoordinateSystem(R, SensorManager.AXIS_Y, SensorManager.AXIS_MINUS_X, outR);		 
		 sensorManager.getOrientation(outR, values);
		 
		 values[0] = (float) Math.toDegrees(values[0]);
		 values[1] = (float) Math.toDegrees(values[1]);
		 values[2] = (float) Math.toDegrees(values[2]);
		 
		 return values;
	 }
	 
	@Override
	public void update(Observable observable, Object data) {		
		int speed = sensorProcessor.getSpeed() * 2;			//Multiple by 2 for more responsive speed changes.
		int rotation = sensorProcessor.getRotation();		
		
		
		//Change the speed and rotation values in the View.
		mManualDriveView.setSpeed(speed);
		mManualDriveView.setRotation(rotation);
		
		
		
		try {					
			//Weed out many of the change speed requests.  To many packets overwhelms the robot.
			if(speed > previousSpeed + 5 || speed < previousSpeed - 5){		// Choose +-5 as a minimum change value 
																			// because it eliminates the need to send	
																			// the majority of packets to the scribbler
																			// with very small unnoticable increments																				
				
				
					previousSpeed = speed;
					service.drive(speed, rotation);							// calling the Scribbler Android Service
																			// where the packet will be built and sent 
																			// to the robot notifying the change
					
				
			}
		} catch (RemoteException e) {
			Log.e(TAG,"Error when calling service.drive().  Check stack trace",e);
			finish();														//Close out the activity.
		}
		
				
		mManualDriveView.invalidate();										// Force the view to redraw with new values.
	}
	
	@Override
	public void onAccuracyChanged(Sensor sensor, int accuracy) {
		//This changes all the time but no important for what the robot is doing.  Ignoring.		
	}
	
	@Override
	public void onSensorChanged(SensorEvent event) {
		if(event.sensor.getType() == Sensor.TYPE_ACCELEROMETER){
			accelerometerValues = event.values;
			
			//Process the new raw values from sensors.
			sensorProcessor.setValues(calculateOrientation());			
		}else if(event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD){
			magneticValues = event.values;

			//Process the new raw values from sensors.
			sensorProcessor.setValues(calculateOrientation());
		}		
	}
	
	@Override
	protected void onResume() {
		super.onResume();
		Sensor accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
		Sensor magField = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
		
		sensorManager.registerListener(this, accelerometer,SensorManager.SENSOR_DELAY_UI);
		sensorManager.registerListener(this, magField,SensorManager.SENSOR_DELAY_UI);
		
	}
	
	@Override
	protected void onStop() {
		sensorManager.unregisterListener(this);
		try {
			service.stop();
		} catch (RemoteException e) {
			Log.e(TAG,"Error when calling service.stop().  Check stack trace",e);			
		}
		finish();
		super.onStop();
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		if(event.getAction() == MotionEvent.ACTION_DOWN){			
			sensorManager.unregisterListener(this);
			sensorProcessor.deleteObserver(this);
			
			sensorProcessor.setValues(sensorProcessor.getStoppedValue());		//Clear the current display values
						
			try {				
				service.stop();								
			} catch (RemoteException e) {
				
				e.printStackTrace();
			}			
			mManualDriveView.setMessage("STOPPED");			
			mManualDriveView.invalidate();		//Redraw the ManualDriveView with new values			
		}else if(event.getAction() == MotionEvent.ACTION_UP){
			
			Sensor accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
			Sensor magField = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
			
			sensorManager.registerListener(this, accelerometer,SensorManager.SENSOR_DELAY_GAME);
			sensorManager.registerListener(this, magField,SensorManager.SENSOR_DELAY_GAME);

			sensorProcessor.addObserver(this);
			mManualDriveView.setMessage("");
			mManualDriveView.invalidate();		//Redraw the ManualDriveView with new values
		}
		return super.onTouchEvent(event);
	}

	
	 
	 /**
	  * Disabling the back button so the user doesn't leave the menu
	  * allowing them to stop the automatic behaviors.
	  * 
	  */
	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_HOME) { 
			//Need to stop the robot and kill the sensors before we exit the activity.						
			try {
				service.stop();
			} catch (RemoteException e) {
				Log.e(TAG,"Error when calling service.stop().  Check stack trace");
				e.printStackTrace();
			}
			finish();			// Stop this activity.
		}		
			return super.onKeyDown(keyCode, event);		
	}
	
	
	/**
	 * Handle view elements when state moves to connected.
	 */
	@Override
	void _connected(String device) {
		//Not used in the view.
	}

	/**
	 * Handle view elements when state moves to connecting.
	 */
	@Override
	void _connecting(String device) {
		//Not used in the view.
	}

	/**
	 * Handle view elements when state moves to disconnected.
	 */
	@Override
	void _disconnect() {
		//finish the view, error message with details of disconnection will be printed as a Toast.
		finish();		
	}

	
	
	
	
}
